use std::{mem::replace, sync::Arc};

use super::{PkgIndexInfo, SolveContext, SolveItem, TermIndex};
use crate::cache::ver::Version;
use anyhow::{bail, Result};
use async_recursion::async_recursion;
use varisat::{ExtendFormula, Lit};
#[derive(Debug, PartialEq)]
enum Operation {
    And,
    Or,
    If,
    Else,
    With,
    Without,
    Unless,
}
// impl Operation {
//     fn from_str(s: &str) -> Option<Operation> {
//         let s = s.trim_start().trim_end();
//         match s {
//             "and" => Some(Operation::And),
//             "or" => Some(Operation::Or),
//             "if" => Some(Operation::If),
//             "else" => Some(Operation::Else),
//             "with" => Some(Operation::With),
//             "without" => Some(Operation::Without),
//             "unless" => Some(Operation::Unless),
//             _ => None,
//         }
//     }
// }
#[derive(Debug, PartialEq)]
enum State {
    Entry(String),
    Term(String),
    Op(Operation),
}

impl SolveContext {
    #[async_recursion]
    pub(super) async fn solve_require_term(
        mut self,
        term: String,
    ) -> Result<(TermIndex, Option<Vec<PkgIndexInfo>>)> {
        let term_index = self.get_latest_index();
        let (ops, term_entry_indexs, pkgs) = self.parse_term(&term).await?;

        if ops.contains(&Operation::Or) {
            let mut lits = term_entry_indexs
                .iter()
                .map(|x| Lit::from_dimacs(*x))
                .collect::<Vec<_>>();
            lits.insert(0, !Lit::from_dimacs(term_index));
            let mut formula_lock = self.formula.lock().await;
            formula_lock.add_clause(&lits);
            drop(formula_lock);
            let mut pkgs_union = Vec::new();
            for ele in pkgs {
                union(&mut pkgs_union, &ele);
            }
            let pkgs = if pkgs_union.is_empty() {
                None
            } else {
                Some(pkgs_union)
            };
            return Ok((term_index, pkgs));
        }
        let lits_vec = if ops.contains(&Operation::And) {
            let mut lits = Vec::new();
            for ele in term_entry_indexs {
                lits.push(vec![!Lit::from_dimacs(term_index), Lit::from_dimacs(ele)]);
            }
            lits
        } else if ops.contains(&Operation::If) && ops.contains(&Operation::Else) {
            // m if p else n
            if term_entry_indexs.len() != 3 {
                bail!(
                    "Failed to parse term {} , 'm if p else n ' should be three operands",
                    term
                );
            }
            vec![
                vec![
                    !Lit::from_dimacs(term_index),
                    !Lit::from_dimacs(term_entry_indexs[1]),
                    Lit::from_dimacs(term_entry_indexs[0]),
                ],
                vec![
                    !Lit::from_dimacs(term_index),
                    Lit::from_dimacs(term_entry_indexs[1]),
                    Lit::from_dimacs(term_entry_indexs[2]),
                ],
            ]
        } else if ops.contains(&Operation::If) {
            // m if p
            if term_entry_indexs.len() != 2 {
                bail!(
                    "Failed to parse term {} , 'm if p' should be two operands",
                    term
                );
            };
            vec![vec![
                !Lit::from_dimacs(term_index),
                !Lit::from_dimacs(term_entry_indexs[1]),
                Lit::from_dimacs(term_entry_indexs[0]),
            ]]
        } else if ops.contains(&Operation::With) {
            // pkgA-foo with pkgA-bar
            // if term == "(python3.11dist(pyasn1) < 0.5~~ with python3.11dist(pyasn1) >= 0.4.6)" {
            //     dbg!(&pkgs);
            // }
            // if term == "(gcc >= 12 with gcc < 13)" {
            //     dbg!(&pkgs);
            // }
            // let mut pkg_with = Vec::new();
            // for ele in pkgs {
            //     with(&mut pkg_with, &ele);
            // }
            let mut pkg_with = pkgs[0].clone();
            for i in 1..pkgs.len() {
                with(&mut pkg_with, &pkgs[i]);
            }
            pkg_with.sort();
            // dbg!(&pkg_with);
            let mut lits = pkg_with
                .iter()
                .map(|x| Lit::from_dimacs(x.pkg_index))
                .collect::<Vec<_>>();
            lits.insert(0, !Lit::from_dimacs(term_index));
            vec![lits]
        } else if ops.contains(&Operation::Without) {
            let mut pkg_without = pkgs[0].clone();
            for i in 1..pkgs.len() {
                without(&mut pkg_without, &pkgs[i]);
            }
            pkg_without.sort();
            let mut lits = pkg_without
                .iter()
                .map(|x| Lit::from_dimacs(x.pkg_index))
                .collect::<Vec<_>>();
            lits.insert(0, !Lit::from_dimacs(term_index));
            vec![lits]
        } else if ops.contains(&Operation::Unless) && ops.contains(&Operation::Else) {
            // m unless p else n
            //t->(p->n) !r,!p,n
            //t->(!p->m) !r,p,m
            if term_entry_indexs.len() != 3 {
                bail!(
                    "Failed to parse term {} , 'm unless p else n ' should be three operands",
                    term
                );
            }
            vec![
                vec![
                    !Lit::from_dimacs(term_index),
                    !Lit::from_dimacs(term_entry_indexs[1]),
                    Lit::from_dimacs(term_entry_indexs[2]),
                ],
                vec![
                    !Lit::from_dimacs(term_index),
                    Lit::from_dimacs(term_entry_indexs[1]),
                    Lit::from_dimacs(term_entry_indexs[0]),
                ],
            ]
        } else if ops.contains(&Operation::Unless) {
            // m unless p
            // r->!p->m !r,p,m
            if term_entry_indexs.len() != 2 {
                bail!(
                    "Failed to parse term {} , 'm unless p' should be two operands",
                    term
                );
            };
            vec![vec![
                !Lit::from_dimacs(term_index),
                Lit::from_dimacs(term_entry_indexs[1]),
                Lit::from_dimacs(term_entry_indexs[0]),
            ]]
        } else {
            let mut lits = term_entry_indexs
                .iter()
                .map(|x| Lit::from_dimacs(*x))
                .collect::<Vec<_>>();
            lits.insert(0, !Lit::from_dimacs(term_index));
            vec![lits]
        };
        let mut formula_lock = self.formula.lock().await;
        for lits in lits_vec {
            formula_lock.add_clause(&lits);
        }
        let mut index_term_lock = self.index_map_term.lock().await;
        index_term_lock.insert(term_index, term);
        drop(formula_lock);
        Ok((term_index, None))
    }
    async fn parse_term(
        &mut self,
        term: &str,
    ) -> Result<(Vec<Operation>, Vec<isize>, Vec<Vec<PkgIndexInfo>>)> {
        let states = parse_term_to_state(term);
        if states.is_none() {
            bail!("Can't parse term {}", term);
        }
        let states = states.unwrap();
        let mut term_entry_indexs = Vec::new();
        let mut pkg_index_infos = Vec::new();
        let mut ops = Vec::new();
        for ele in states {
            match ele {
                State::Entry(entry) => {
                    let (entry_index, pkg_indexs) = self
                        .clone()
                        .solve_require_item(SolveItem::from_str(&entry))
                        .await?;
                    term_entry_indexs.push(entry_index);
                    pkg_index_infos.push(pkg_indexs);
                    // union(&mut pkg_index_infos, &pkg_indexs);
                }
                State::Term(sub_term) => {
                    let (term_index, pkg_indexs) =
                        self.clone().solve_require_term(sub_term).await?;
                    term_entry_indexs.push(term_index);
                    if let Some(pkg_indexs) = pkg_indexs {
                        pkg_index_infos.push(pkg_indexs);
                        // union(&mut pkg_index_infos, &pkg_indexs);
                    }
                }
                State::Op(op) => {
                    ops.push(op);
                }
            }
        }
        if term_entry_indexs.len() - ops.len() != 1 {
            bail!(
                "Failed to parse term {} , there are {} operands but {} term or entry",
                term,
                ops.len(),
                term_entry_indexs.len()
            );
        }
        Ok((ops, term_entry_indexs, pkg_index_infos))
    }
}
fn parse_term_to_state(s: &str) -> Option<Vec<State>> {
    if let Some((_, rest)) = s.split_once('(') {
        if let Some((rest, _)) = rest.rsplit_once(')') {
            let mut states = Vec::new();
            let mut iter = rest.chars();
            let mut rest = String::new();
            let mut pre_char = ' ';
            loop {
                if let Some(ch) = iter.next() {
                    if pre_char.is_whitespace() && ch == '(' {
                        //get term
                        rest.clear();
                        let mut term = String::new();
                        let mut count = 1;
                        term.push('(');
                        loop {
                            if let Some(c) = iter.next() {
                                if c == '(' {
                                    count += 1;
                                } else if c == ')' {
                                    count -= 1;
                                }
                                term.push(c);
                                if count == 0 {
                                    break;
                                }
                            } else {
                                break;
                            }
                        }
                        states.push(State::Term(term));
                    } else {
                        rest.push(ch);
                        pre_char = ch;
                        let (entry, op) = get_op(rest.clone());
                        if let Some(o) = op {
                            if let Some(p) = entry {
                                states.push(State::Entry(p));
                            }
                            states.push(State::Op(o));
                            rest.clear();
                        }
                    }
                } else {
                    break;
                }
            }
            let rest = rest.trim_end().trim_start();
            if rest != "" {
                states.push(State::Entry(rest.to_owned()));
            }
            return Some(states);
        }
    };
    None
}
fn get_op(s: String) -> (Option<String>, Option<Operation>) {
    let s = s.trim_end();
    let (op, s) = if s.ends_with(" and") {
        (Some(Operation::And), s.trim_end_matches("and"))
    } else if s.ends_with(" or") {
        (Some(Operation::Or), s.trim_end_matches("or"))
    } else if s.ends_with(" if") {
        (Some(Operation::If), s.trim_end_matches("if"))
    } else if s.ends_with(" else") {
        (Some(Operation::Else), s.trim_end_matches("else"))
    } else if s.ends_with(" with") {
        (Some(Operation::With), s.trim_end_matches("with"))
    } else if s.ends_with(" without") {
        (Some(Operation::Without), s.trim_end_matches("without"))
    } else if s.ends_with(" unless") {
        (Some(Operation::Unless), s.trim_end_matches("unless"))
    } else {
        (None, s)
    };
    let entry = if op.is_some() {
        let s = s.trim_end().trim_start();
        if s == "" {
            None
        } else {
            Some(s.to_string())
        }
    } else {
        None
    };
    (entry, op)
}

impl SolveItem {
    pub fn from_str(p: &str) -> Self {
        let ((pkg_name, ver), flag) = if p.find("<=").is_some() {
            (p.split_once("<=").unwrap(), "LE")
        } else if p.find("<").is_some() {
            (p.split_once("<").unwrap(), "LT")
        } else if p.find(">=").is_some() {
            (p.split_once(">=").unwrap(), "GE")
        } else if p.find(">").is_some() {
            (p.split_once(">").unwrap(), "GT")
        } else if p.find("=").is_some() {
            (p.split_once("=").unwrap(), "EQ")
        } else {
            ((p, ""), "")
        };
        let flag = if flag == "" {
            None
        } else {
            Some(flag.to_owned())
        };
        SolveItem {
            entry_name: Arc::new(pkg_name.trim().to_owned()),
            ver: Version::from_str(ver),
            flag,
        }
    }
}

fn with(left: &mut Vec<PkgIndexInfo>, right: &Vec<PkgIndexInfo>) {
    let mut r = Vec::new();
    if left.len() > right.len() {
        for ele in right {
            if left.contains(ele) {
                r.push(*ele);
            };
        }
    } else {
        for ele in left.iter() {
            if right.contains(ele) {
                r.push(*ele);
            };
        }
    }
    let _ = replace(left, r);
}
fn without(left: &mut Vec<PkgIndexInfo>, right: &Vec<PkgIndexInfo>) {
    let mut r = Vec::new();
    for ele in left.iter() {
        if !right.contains(ele) {
            r.push(*ele);
        }
    }
    // r
    let _ = replace(left, r);
}
fn union(left: &mut Vec<PkgIndexInfo>, right: &Vec<PkgIndexInfo>) {
    for ele in right {
        if !left.contains(ele) {
            left.push(*ele);
        }
    }
}
#[cfg(test)]
mod test {
    use std::{mem::replace, sync::Arc};

    use crate::{
        cache::ver::Version,
        solve::{
            solve_term::{parse_term_to_state, without, Operation, State},
            PkgIndexInfo, SolveItem,
        },
    };

    #[test]
    fn test_get_term() {
        assert_eq!(
            parse_term_to_state("((pam >= 1.3.1-15) if openssh)"),
            Some(vec![
                State::Term("(pam >= 1.3.1-15)".to_owned()),
                State::Op(Operation::If),
                State::Entry("openssh".to_owned()),
            ])
        );
        assert_eq!(
            parse_term_to_state("(pam >= 1.3.1-15)"),
            Some(vec![State::Entry("pam >= 1.3.1-15".to_owned()),])
        );
        assert_eq!(
            parse_term_to_state("(( A(64) < 1 or B ) if C <= 2.3-fc37 else (D with E))"),
            Some(vec![
                State::Term("( A(64) < 1 or B )".to_owned()),
                State::Op(Operation::If),
                State::Entry("C <= 2.3-fc37".to_owned()),
                State::Op(Operation::Else),
                State::Term("(D with E)".to_owned())
            ])
        );
        assert_eq!(
            parse_term_to_state("(( A(64) < 1 or B ) and C <= 2.3-fc37 and D(64) < 2)"),
            Some(vec![
                State::Term("( A(64) < 1 or B )".to_owned()),
                State::Op(Operation::And),
                State::Entry("C <= 2.3-fc37".to_owned()),
                State::Op(Operation::And),
                State::Entry("D(64) < 2".to_owned())
            ])
        );
        assert_eq!(
            parse_term_to_state("((fcitx5-qt6(x86-64) = 5.0.15-1.fc37) if qt6-qtbase)"),
            Some(vec![
                State::Term("(fcitx5-qt6(x86-64) = 5.0.15-1.fc37)".to_owned()),
                State::Op(Operation::If),
                State::Entry("qt6-qtbase".to_owned())
            ])
        );
        assert_eq!(
            parse_term_to_state(
                "((python3.11dist(google-api-core) < 2.1~~ or python3.11dist(google-api-core) >= 2.2) 
        with (python3.11dist(google-api-core) < 2.2~~ or python3.11dist(google-api-core) >= 2.3)
        with (python3.11dist(google-api-core) < 2.3 or python3.11dist(google-api-core) > 2.3) 
        with (python3.11dist(google-api-core) < 2~~ or python3.11dist(google-api-core) >= 2.1) 
        with python3.11dist(google-api-core) < 3~~dev0 
        with python3.11dist(google-api-core) >= 1.31.5)"
            ),
            Some(vec![State::Term(
                "(python3.11dist(google-api-core) < 2.1~~ or python3.11dist(google-api-core) >= 2.2)"
                    .to_owned()
            ),
            State::Op(Operation::With),
            State::Term("(python3.11dist(google-api-core) < 2.2~~ or python3.11dist(google-api-core) >= 2.3)".to_owned()),
            State::Op(Operation::With),
            State::Term("(python3.11dist(google-api-core) < 2.3 or python3.11dist(google-api-core) > 2.3)".to_owned()),
            State::Op(Operation::With),
            State::Term("(python3.11dist(google-api-core) < 2~~ or python3.11dist(google-api-core) >= 2.1)".to_owned()),
            State::Op(Operation::With),
            State::Entry("python3.11dist(google-api-core) < 3~~dev0".to_owned()),
            State::Op(Operation::With),
            State::Entry("python3.11dist(google-api-core) >= 1.31.5".to_owned()),
            ])
        )
    }

    #[test]
    fn test_from_str() {
        assert_eq!(
            SolveItem::from_str("python3.11dist(niapy) >= 1.24.1-1.fc37"),
            SolveItem {
                entry_name: Arc::new("python3.11dist(niapy)".to_owned()),
                ver: Version {
                    epoch: None,
                    version: Some("1.24.1".to_owned()),
                    release: Some("1.fc37".to_owned())
                },
                flag: Some("GE".to_owned())
            }
        );
        assert_eq!(
            SolveItem::from_str("python3.11dist(niapy) <= 1.24.1"),
            SolveItem {
                entry_name: Arc::new("python3.11dist(niapy)".to_owned()),
                ver: Version {
                    epoch: None,
                    version: Some("1.24.1".to_owned()),
                    release: None
                },
                flag: Some("LE".to_owned())
            }
        );
    }
    #[test]
    fn test_term() {
        if let Some(s) = parse_term_to_state("((fcitx5-qt6(x86-64) = 5.0.15-1.fc37) if qt6-qtbase)")
        {
            if s.contains(&State::Op(Operation::If)) {
                assert!(true)
            } else {
                assert!(false)
            }
        };
    }
    fn with_a(left: &mut Vec<PkgIndexInfo>, right: &Vec<PkgIndexInfo>) {
        let mut r = Vec::new();
        if left.len() > right.len() {
            for ele in right {
                if left.contains(ele) {
                    r.push(*ele);
                };
            }
        } else {
            for ele in left.iter() {
                if right.contains(ele) {
                    r.push(*ele);
                };
            }
        }
        let _ = replace(left, r);
    }
    #[test]
    fn test_with() {
        let pkgs = vec![
            vec![PkgIndexInfo {
                pkg_index: 812,
                arch_priority: 2,
                repo_priority: 0,
            }],
            vec![PkgIndexInfo {
                pkg_index: 812,
                arch_priority: 2,
                repo_priority: 0,
            }],
        ];
        let mut pkg_with = pkgs[0].clone();
        for i in 1..pkgs.len() {
            with_a(&mut pkg_with, &pkgs[i]);
        }

        // for ele in pkgs {
        //     with_a(&mut pkg_with, &ele);
        // }
        pkg_with.sort();
        dbg!(&pkg_with);
    }
    #[test]
    fn test_without() {
        let mut left = vec![
            PkgIndexInfo {
                pkg_index: 1,
                arch_priority: 4,
                repo_priority: 0,
            },
            PkgIndexInfo {
                pkg_index: 2,
                arch_priority: 4,
                repo_priority: 0,
            },
            PkgIndexInfo {
                pkg_index: 3,
                arch_priority: 5,
                repo_priority: 0,
            },
        ];
        let right = vec![
            PkgIndexInfo {
                pkg_index: 2,
                arch_priority: 4,
                repo_priority: 0,
            },
            PkgIndexInfo {
                pkg_index: 6,
                arch_priority: 5,
                repo_priority: 0,
            },
        ];
        without(&mut left, &right);
        assert_eq!(
            left,
            vec![
                PkgIndexInfo {
                    pkg_index: 1,
                    arch_priority: 4,
                    repo_priority: 0,
                },
                PkgIndexInfo {
                    pkg_index: 3,
                    arch_priority: 5,
                    repo_priority: 0,
                }
            ]
        )
    }
    #[test]
    fn test_op() {
        let ops = vec![Operation::If, Operation::Else];
        // assert_eq!(ops, vec![Operation::If, Operation::Else]);
        if ops == vec![Operation::If, Operation::Else] {
            assert!(true)
        } else {
            assert!(false)
        }
    }
}
